Skip to content

Abstractions: absorb 2026-11-10 — PRD-872 complete in-repo, PRD-873 store change (batch PR) - #662

Open
jonit-dev wants to merge 14 commits into
developfrom
absorb-2026-11-10
Open

jonit-dev wants to merge 14 commits into
developfrom
absorb-2026-11-10

Conversation

@jonit-dev

@jonit-dev jonit-dev commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

One PR for the whole absorb batch, as asked. It carries PRD-872 end to end in this repository, plus PRD-873's store change. ObjectPool, the RigidBody3D rest options, the prewarm restore and all of PRD-874 are open — the reasons are below the landed work.

Review: fresh Opus 5.5 at high effort (Claude Code), verdict BLOCK → fixed

The first pass returned BLOCK with two findings. Both are fixed in b29aca3af, and the reviewer's non-blocking notes are addressed or recorded:

Finding Fix
Core boundary: createRandom imported into 13 kit src/render/ files; check-core-boundary fails (runs in budgets and CI) A kit's render layer may not import a core symbol outside RENDER_PORTABLE_CORE_SYMBOLS. The 13 files keep their own generators and the PRNG row excludes src/render/**; the boundary check passes. The row still covers a level, an entity, a scene, a sim.
Lock-on trail: leaf-only updateWorldMatrix left the target's root/groups a frame behind target.mesh.updateWorldMatrix(true, true) once per frame
damp row matched any X - Math.exp(…) selector requires the literal 1 on the left
matrix row's message ignored a single static node message names markStatic(node) as well as renderer.matrixWorld
no spec actually lints the 16 kits (the PRD cited a script that does not run ESLint) new case in quality-rules.spec.ts: all 16 templates/*/src trees under the hot rules + the six rows at error, failing closed under 300 files. It found two suppressions that had gone unused and would have failed a generated game's lint.
PRNG fixture could not show Math.random, the LCG pair and 0x6d2b79f5 failing separately three fixtures; the spec also asserts InputMap.prototype.axis and debugValue exist
PRD over-claims corrected in the PRD text: check-template-quality does not run ESLint, the render PRNG revert, the playtest result, and the measured 4 kit changed-only checks (the survey said 5)

Recorded, not fixed: state.set compares Object.is, so 0/-0 now publishes (documented in the store's JSDoc); racing mutates lastOnRoad in place and publishes because sibling keys change (the documented catch); ?still=0 now means off, which is the engine's one launch vocabulary.

Landed

PRD-872 — the lint names the engine call

  • Phase 1: trackHotCallbacks treats a non-static update/fixedUpdate/tick/step on any class as per-frame. 12 real sites in 9 kits, 0 false positives. Fixes: 9 touch-controls.ts files reuse one knobPosition; Warden.update reuses toHero; LockOn.update caches the target's mesh list and the reticle box.
  • Phase 2: src/quality/reinventions.ts (6 rows → ESLint's no-restricted-syntax), each row's capability resolved against the manifest or three. 109 sites replaced outside src/render/: launch seams (debugFlag/debugValue), 23 damp factors, 38 smoothsteps. debugValue(name) exported from core.
  • Phase 3 (engine half): ctx.assets.memo, ctx.startup.progress, ctx.after, ctx.beforeRender have manifest rows (scripts/ctx-capabilities.ts, the render-chain registry pattern; interface-member JSDoc is not indexed), 571 fresh entries; the four search queries return them. debugValue is indexed from its JSDoc.

PRD-873 — the engine owns the pool and the body's rest (partial)

  • Phase 2, store half: a key whose value is Object.is-equal to the current one no longer enters pending, so a frame that changed nothing publishes nothing. The 4 hand-written changed-only checks are deleted (starter 11 fields, action-rpg 12, tower-defense's sameState, runner 5); runner floors distance, which the HUD always did. 3 new spec cases, red on the previous store.

Open, and why

  • PRD-872 AC-2 / sandbox box: starlance, midway-open-pacific, stress-lab are a separate checkout. Not run, not claimed.
  • PRD-873 ObjectPool, RigidBody3D rest options, prewarm restore: open. The pool changes the draw path and owes a visual judge; the body options need the Rust physics crate and a native build/desktop playtest in the same commit; the prewarm restore changes renderer semantics and needs the GPU harness. None of those could be proven here.
  • PRD-874: not started — ~850 generated lines across 16 kits plus native/Android proofs.
  • Kit playtests: pnpm test:templates was run after node scripts/build-wasm.mjs (which cleared TN_PACK_WEB_ENGINE_STALE). It fails for environment reasons: 8 kits TN_PLAYTEST_PERF_FEW_GPU_SAMPLES (software adapter), 6 templates' dev servers miss the 15 s readiness window while cooking KTX2, 2 TN_PLAYTEST_ENGINE_COST_OVER_BOUND under software. No green playtest is claimed.
  • Visual judge: not run. The only appearance-affecting change left is none in the kits — the render PRNG revert removed it; the reticle math is equivalent with the chain update.

Gates actually run (this head)

Gate Result
pnpm exec vitest run packages/create-threenative/__tests__/quality-rules.spec.ts 18 passed, incl. the 16-kit gate (3.8 s)
pnpm exec vitest run packages/core/__tests__/state.spec.ts 9 passed (red on the previous store: 2 failed)
pnpm exec vitest run packages/create-threenative/__tests__/scaffold.spec.ts 79 passed (pins re-recorded)
pnpm exec vitest run packages/engine-mcp/__tests__/search.spec.ts 108 passed
pnpm typecheck clean (root + packages)
pnpm exec tsx scripts/check-core-boundary.ts pass (failed on 13 render files before the fix)
pnpm capabilities:check 571 fresh entries
pnpm api:surface:check ok; no breaking entry needed
pnpm check:docs 3918 links, port status current
pnpm ci:fast agents, docs, lint, drift pass
pnpm test:templates failed for environment reasons, listed above

Second review pass (Opus 5.5 high): one finding left, fixed in 7d057342c

Finding 1 (boundary) verified fixed. The re-review showed the lock-on lag was frame order, not the matrix update: lock.update runs before the hero reads its target, while the creatures it frames move later in the same frame.

  • LockOn.update now keeps only the selection; place(camera, dt) owns the bounds and the reticle and runs after the warden, mosslings, foes and dummies have moved (adventure/src/scenes/Play.ts:778).
  • PRD counts corrected to what git grep measures: 38 smoothstep expressions, 24 damp calls from 22 expressions, 3 launch seams, 3 LCGs outside src/render/; 2 suppressions remain (adventure level, rts replay).
  • PRD-873's state proof now names the four kits that actually changed (starter, action-rpg, tower-defense, runner).

Remaining caveat from the re-review, recorded not hidden: the kit-wide spec applies the three hot rules and the six reinvention rows, not the full shipped qualityConfig (typed rules, react-hooks, startup-override-reason), so it does not by itself prove a generated game's whole pnpm lint is green.

Capability discovery: every abstraction this branch adds is searchable and resolvable

Checked against the built manifest through the MCP search code (searchCapabilities + capabilityDetail):

Query an author types First row Detail
cache generated geometry between page loads ctx.assets.memo signature, 5 situations, example
boot progress bar ctx.startup.progress signature, 5 situations, example
run a callback after a delay ctx.after signature, 4 situations, example
run a callback before the frame renders ctx.beforeRender signature, 4 situations, example
read a debug override whose value is not a boolean debugValue signature, 2 situations, example
seeded deterministic random number generator createRandom resolved
map WASD keys to a movement axis InputMap resolved

pnpm caps:recall exits 0 with the same 7 measured not-owned misses as develop (no new misses). pnpm exec vitest run packages/engine-mcp/__tests__/{server,capability-examples,search-gold,search}.spec.ts — 138 passed, including "finds every exact symbol name the manifest exports as the first row" and "type-checks every hand-authored example against the type it demonstrates".

Gates run after the capability check

Gate Result
pnpm budgets (hard invariants: workspace pins, native shims, core boundary, capability-docs census, caps:recall, evidence budget, VFX coverage, budgets, generated ctx/reference tables, realism docs/conformance/matrix, capability duplicates, template quality) pass
pnpm caps:recall exit 0, 7 measured not-owned misses
pnpm exec vitest run packages/engine-mcp/__tests__/{server,capability-examples,search-gold}.spec.ts 30 passed
pnpm test under Node 20.19.6 (the version procedural-animals requires; the shell default is 22) web lane pass — every package except @threenative/runtime-native; 19/19 and 46/46 in the last two packages, no failure
pnpm test @threenative/runtime-native not runnable here: TN_PUMP_PROBE_NO_BINARY — the desktop host is not built (pnpm native:build is opt-in). Node 22 additionally fails procedural-animals with TN_ANIMAL_BUILD_RUNTIME; under Node 20 that package is 166/166

CI on 92b2749fe: green

Run 42970102867 — Change scope, ci-required, lint, supply-chain, budgets, build-artifacts, typecheck, test, test-unit (1/1), golden-path, golden-path-template (action-rpg) all pass. The first ready-for-review run caught two real regressions from the rows, both fixed in 92b2749fe:

  • rts/src/sim/terrain.ts imported MathUtils from three, and rts-runtime-cost.spec asserts a sim module imports no three; the inline form returns with a described suppression naming that spec.
  • rain's GameUi reads the engine's debugValue seam, and the unit test stubbed only window.location; the stub now shares one object with globalThis.location, so the URL-tier test exercises the real path.

GPU retry on the branch (5f0e37694)

Run from a private worktree of this branch, Node 20, GPU idle (RTX 2080), TN_PLAYTEST_ALLOW_SOFTWARE unset:

  • Fully passing scaffolded suites: adventure, collectathon, kart, puzzle, snow.
  • Kit contract: green except rows that also fail on origin/develop in the same environment (platformer sight line, shooter worker RangeError: Offset is outside the bounds of the DataView) and frame-cost rows under a CPU load of 8–13 from other agents sharing this machine (no-hitch few-GPU-samples, engine-cost over 16.7 ms). action-rpg restart-resets failed once in the sweep and is green when run alone.
  • Still not green as a sweep, so PRD-872 Phase 2's playtest box stays open: the repo runs this sweep in the schedule-only hardware lane (performance-regression.yml), not on the PR board.

5f0e37694 (the PRD-evidence commit) is green on run 43091324878 after one flake rerun: golden-path-template (action-rpg) failed at ./scaffold.sh action-rpg (2 s, no output) and the rerun of the same commit passed.

Merged origin/develop (b01de92c7)

  • One conflict: starter's scaffold pin; recomputed from the merged tree (16 kits, 1 moved).
  • On the merged tree: pnpm typecheck clean · pnpm ci:fast pass · pnpm budgets pass (hard invariants, capability census, duplicates, generated tables, core boundary, template quality) · capabilities:check fresh (571) · api:surface:check ok · quality-rules 18 · scaffold 79 · rain-quality 10 · rts-runtime-cost 8 · state 9 · engine-mcp search 108.
  • ⚠️ No CI run has been created for the merged head (7a2c03d17): three pushes produced 0 runs and 0 check-runs for the branch, and the manual workflow_dispatch on absorb-2026-11-10 was cancelled. Repo-wide, only CI janitor runs appeared in that window. Until Actions creates a run, the PR cannot show ci-required green; the merge itself is validated locally above.

Second merge: origin/develop at c1c25a657 (main promotion merge)

Pushed as 3d4644799. Conflict again only the starter scaffold pin; every pin recomputed (16 kits moved — a shared input changed in develop). On the merged tree: pnpm typecheck clean, ci:fast pass, capabilities:check fresh, api:surface:check ok, quality-rules/scaffold/state/rain/rts 124 passed, engine-mcp search 109 passed, kit-wide lint gate green.

CI on the merge (43152340235) is red only for items that are already red on origin/develop's own tree, each verified against develop itself:

Red Where it comes from Evidence
check-budgets: "native coverage report is stale: source digest changed to aa2574…" the main→develop promotion merge brought native sources without the regenerated report; the regeneration exists uncommitted in another agent's local develop (ba9df86d4) the same check fails on a tree that contains only develop + this branch's non-native files
capability-manifest.spec: alias corpus 59 vs 53 PRD-880's six pad aliases; the matching corpus row is another agent's local develop commit local commit message: "test(capabilities): the six pad aliases name a corpus row"
game.spec: input.vector("move") returns [0.401, 0.2007] not [0.5, 0.25] PRD-880's input rework; the spec is untouched by this branch reproduced locally on the merge; git diff origin/develop touches no input code
template.spec: action-rpg 1231 / starter 1234 words, limit 1200 shared inline agent-doc text; measured identical with this branch's reference-doc line reverted the audit reads inline fragments, not reference pages

Nothing in that list is this branch's code, and each fix belongs on develop (the alias one is already staged there).

debugValue(name) is debugFlag's non-boolean sibling: ?quality= on the web,
TN_DEBUG_QUALITY on a native launch. The four ctx members an author searches
for (assets.memo, startup.progress, after, beforeRender) are interface
properties, so walking exports never found them; they now live in
scripts/ctx-capabilities.ts, the same hand-authored registry the render-chain
config keys use. Manifest gains 5 rows: 571 entries, pnpm capabilities:check
fresh.
…e call

trackHotCallbacks treats a non-static update, fixedUpdate, tick or step on any
class as per-frame, so the three hot-callback rules reach a game's own entity
classes; 12 real sites in 9 kits, 0 false positives.

src/quality/reinventions.ts is a data table of the six hand-written forms a
shipped capability already covers, fed to no-restricted-syntax. Each row's
capability is resolved against capabilities.json or three by the spec, so a
message cannot name a removed symbol.
109 hand-written forms replaced across the 16 kits: 38 inline smoothsteps,
23 exponential damp factors, 34 PRNG copies, 3 location.search launch seams.
The LCG copies were the engine's own sequence and produce identical values
through createRandom; the authored mulberry32 streams (adventure level, kart
course, rts world, rain storm) keep a described suppression because swapping
them builds different content.

Scaffold pins re-recorded; scaffold spec 79 passed. PRD-872 ticks carry their
proof; the two sandbox boxes stay open (separate checkout).
@jonit-dev jonit-dev added the prd:50% PRD half the phases verified label Nov 11, 2026
A key whose value is Object.is-equal to the current one does not enter
pending, so a frame that changed nothing publishes nothing and flush stays
quiet. Four kits carried the comparison by hand (starter's 11 fields,
action-rpg's 12, tower-defense's sameState, runner's five) and the store now
owns it; runner floors distance where the HUD always did, so a fractional
odometer cannot republish every frame.

state.spec.ts: 3 new cases, red on the previous store (2 failed).
…uns on every kit

Review findings from the fresh Opus pass:

- A kit's src/render/ may not import a core symbol outside
  RENDER_PORTABLE_CORE_SYMBOLS, so the six clouds copies, collectathon's
  paint tables, platformer's decoration, snow's scenery and the shooter and
  tower-defense shape streams go back to their own generators, and the PRNG
  row excludes src/render/** (it still covers a level, an entity, a scene).
  check-core-boundary passes again; it failed on 13 render files.
- LockOn updates the target's whole world-matrix chain once per frame; a
  leaf-only update left the reticle one frame behind.
- The damp row requires the literal 1 on the left, so a fog factor cannot
  match; the matrix row's message names markStatic for a single static node.
- The kit-wide gate is now a spec: all 16 templates/*/src trees linted with
  the hot rules and the six rows at error, failing closed under 300 files.
  It found two suppressions that had gone unused and would have failed a
  generated game's pnpm lint.
- PRD claims corrected: check-template-quality does not run ESLint, the
  render PRNG revert, the playtest run's environmental failures, and the
  4 measured kit changed-only checks (not 5).

state.spec.ts 9 passed, quality-rules 18 passed (incl. the kit gate),
scaffold 79 passed, capabilities fresh, ci:fast pass.
@jonit-dev jonit-dev changed the title absorb 2026-11-10 — PRD-872: the lint sees every frame method and names the engine call (batch PR) absorb 2026-11-10 — PRD-872 complete in-repo, PRD-873 store change (batch PR) Nov 11, 2026
Second review: the widened world-matrix update was not the cause of the
one-frame trail — the frame order was. lock.update runs before the hero reads
its target, while the creatures it frames move later in the same frame, so
bounds and reticle were always a pose behind.

LockOn.update keeps the selection; place(camera, dt) owns the bounds and the
reticle and runs after warden, mosslings, foes and dummies have moved.
PRD counts corrected to what git grep measures (38 smoothstep, 24 damp calls
from 22 expressions, 3 launch seams, 3 LCGs), and the 873 proof names the
four kits that changed.
@jonit-dev jonit-dev changed the title absorb 2026-11-10 — PRD-872 complete in-repo, PRD-873 store change (batch PR) Abstractions: absorb 2026-11-10 — PRD-872 complete in-repo, PRD-873 store change (batch PR) Nov 11, 2026
@jonit-dev
jonit-dev marked this pull request as ready for review November 11, 2026 05:47
The fresh shooter scaffold passes typecheck, the new lint and format; the
remaining failures are the software adapter (kit-contract engine-cost, a
playtest-worker RangeError) and cold asset cooking. The CI board carries the
playtest proof.
…the engine seam

CI's unit lane found the two places the rows changed a contract:

- rts/src/sim/terrain.ts imported MathUtils from three for the smoothstep,
  and rts-runtime-cost.spec asserts a sim module imports no three. The inline
  form returns with a described suppression naming that spec.
- rain's GameUi reads the engine's debugValue seam now, and the unit test
  stubbed only window.location; a browser's window.location is the same
  object as globalThis.location, so the stub shares one object and the URL
  tier test exercises the real path again.

quality-rules 18, rain-quality 10, rts-runtime-cost 8, scaffold 79: pass.
With the GPU idle the branch passes adventure, collectathon, kart, puzzle and
snow outright; the remaining rows either also fail on origin/develop in the
same environment (platformer sight line, shooter worker RangeError) or are
frame-cost rows under a CPU load of 9-13 from other agents. action-rpg's
restart row is green when run alone. No green sweep, so the box stays open.
Only the starter scaffold pin conflicted; every pin is recomputed from the
merged tree in the commit that follows.
develop's starter kit change and this branch's both land in the tree the pin
hashes; 16 kits, 1 moved. quality-rules 18, scaffold 79, rain-quality 10,
rts-runtime-cost 8, state 9, engine-mcp search 108, typecheck and ci:fast
pass on the merge.
Pins recomputed from the merged tree in the next commit.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prd:50% PRD half the phases verified

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant